/* -*- Mode: c; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
  RefObject (http://code.google.com/p/refobject/)
  Copyright (C) 2010-2011 MIURA Shirow (sharow)
*/

#include "RefBuildValueInternal.h"
#include "RefModuleInternal.h"

#include "RefAssert.h"
#include <RefObject/RefObjectAll.h>

#ifdef REFOBJECT_HAS_STDC_STDARG
#include <stdarg.h>
#endif


typedef struct {
#ifdef REFOBJECT_HAS_STDC_STDARG
    va_list *mVaList;
#else
    int mDummy;
#endif
} RefArgVaList;

typedef struct {
    RefObject *mRefList;
    int mIndex;
} RefArgRefList;

typedef enum {
    RefArgType_VALIST,
    RefArgType_REFLIST
} RefArgType;

typedef struct {
    RefArgType mType;
    union {
        RefArgVaList mVaList;
        RefArgRefList mArgRefList;
    } uList;
} RefBuildValueArgs;


typedef struct {
    const char *mFormatStr;
    int mFormatStrIndex;
    RefBuildValueArgs *mArgs;
    RefObject *mRefAllocator;
} RefBuildValueContext;


static RefObject *
RefBuildValueContext_BuildValue(RefBuildValueContext *aContext);


/* -- arg */

#ifdef REFOBJECT_HAS_STDC_STDARG
static int
RefBuildValueArgs_InitializeVaArgs(RefBuildValueArgs *aBuildValueArgs, va_list *aVaList)
{
    aBuildValueArgs->mType = RefArgType_VALIST;
    aBuildValueArgs->uList.mVaList.mVaList = aVaList;
    return 0;
}
#endif

static int
RefBuildValueArgs_InitializeRefList(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefList)
{
    aBuildValueArgs->mType = RefArgType_REFLIST;
    aBuildValueArgs->uList.mArgRefList.mRefList = aRefList;
    RefObject_IncRef(aRefList);
    aBuildValueArgs->uList.mArgRefList.mIndex = 0;
    return 0;
}

static void
RefBuildValueArgs_Finalize(RefBuildValueArgs *aBuildValueArgs)
{
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        RefObject_DecRef(aBuildValueArgs->uList.mArgRefList.mRefList);
    } else {
        /* do nothing */
    }
}

static void
RefBuildValueArgs_RefList_GetMembers(RefBuildValueArgs *aBuildValueArgs,
                                     RefObject **outRefList, int *outIndex)
{
    assert(aBuildValueArgs->mType == RefArgType_REFLIST);
    *outRefList = aBuildValueArgs->uList.mArgRefList.mRefList;
    *outIndex = aBuildValueArgs->uList.mArgRefList.mIndex;
}

static RefObject *
RefBuildValueArgs_RefList_GetCurrentItem(RefBuildValueArgs *aBuildValueArgs)
{
    RefObject *reflist;
    int index;
    RefBuildValueArgs_RefList_GetMembers(aBuildValueArgs, &reflist, &index);
    return RefList_GetItem(reflist, index);
}

static int
RefBuildValueArgs_RefList_AdvanceIndex(RefBuildValueArgs *aBuildValueArgs)
{
    int index;
    RefObject *reflist;
    RefBuildValueArgs_RefList_GetMembers(aBuildValueArgs, &reflist, &index);
    if (RefList_GetSize(reflist) <= index) {
        /* index is tail */
        return 1;
    }
    aBuildValueArgs->uList.mArgRefList.mIndex += 1;
    return 0;
}

static RefObject *
RefBuildValueArgs_RefList_GetNextRefObject(RefBuildValueArgs *aBuildValueArgs)
{
    RefObject *value;
    value = RefBuildValueArgs_RefList_GetCurrentItem(aBuildValueArgs);
    if (!value) {
        return NULL;
    }
    if (RefBuildValueArgs_RefList_AdvanceIndex(aBuildValueArgs)) {
        assert(0);
        return NULL;
    }
    return value;
}

/*  generic getters (new reference) */

static RefObject *
RefBuildValueArgs_GetRefObject(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        value = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, RefObject *);
        if (!value) {
            return NULL;
        }
        RefObject_IncRef(value);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetString(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefString_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        unsigned char *str;
        str = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, unsigned char *);
        value = RefString_CreateDuplicateWithAllocator(str, -1, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetVoidPtr(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefVoidPtr_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        void *vp;
        vp = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, void *);
        value = RefVoidPtr_CreateWithAllocator(vp, NULL, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetInt(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefInt_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        int i;
        i = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, int);
        value = RefInt_CreateWithAllocator(i, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetUInt(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefUInt_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        unsigned int u;
        u = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, unsigned int);
        value = RefUInt_CreateWithAllocator(u, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetLong(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefLong_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        long l;
        l = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, long);
        value = RefLong_CreateWithAllocator(l, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetULong(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefULong_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        unsigned long ul;
        ul = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, unsigned long);
        value = RefULong_CreateWithAllocator(ul, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetFloat(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefFloat_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        float f;
        f = (float)va_arg(*aBuildValueArgs->uList.mVaList.mVaList, double);
        value = RefFloat_CreateWithAllocator(f, aRefAllocator);
#endif
    }
    return value;
}

static RefObject *
RefBuildValueArgs_GetDouble(RefBuildValueArgs *aBuildValueArgs, RefObject *aRefAllocator)
{
    RefObject *value;
    value = NULL;
    if (aBuildValueArgs->mType == RefArgType_REFLIST) {
        value = RefBuildValueArgs_RefList_GetNextRefObject(aBuildValueArgs);
        if (!value) {
            return NULL;
        }
        if (!RefDouble_IsValid(value)) {
            return NULL;
        }
        RefObject_IncRef(value);
    } else {
#ifdef REFOBJECT_HAS_STDC_STDARG
        double d;
        d = va_arg(*aBuildValueArgs->uList.mVaList.mVaList, double);
        value = RefDouble_CreateWithAllocator(d, aRefAllocator);
#endif
    }
    return value;
}

/* -- RefBuildValueContext */

static int
RefBuildValueContext_Initialize(RefBuildValueContext *aContext, RefObject *aRefAllocator,
                                const char *aFormatStr, RefBuildValueArgs *aArgs)
{
    aContext->mFormatStr = aFormatStr;
    aContext->mFormatStrIndex = 0;
    aContext->mArgs = aArgs;
    aContext->mRefAllocator = aRefAllocator;
    RefObject_IncRef(aRefAllocator);
    return 0;
}

static void
RefBuildValueContext_Finalize(RefBuildValueContext *aContext)
{
    RefObject_DecRef(aContext->mRefAllocator);
}

static char
RefBuildValueContext_GetCurrentFormatChar(RefBuildValueContext *aContext)
{
    return aContext->mFormatStr[aContext->mFormatStrIndex];
}

static void
RefBuildValueContext_AdvanceFormatCharWhileSpace(RefBuildValueContext *aContext)
{
    while (aContext->mFormatStr[aContext->mFormatStrIndex] == ' ') {
        aContext->mFormatStrIndex += 1;
    }
}

static void
RefBuildValueContext_AdvanceFormatCharJustOne(RefBuildValueContext *aContext)
{
    if (aContext->mFormatStr[aContext->mFormatStrIndex] == '\0') {
        return;
    }
    aContext->mFormatStrIndex += 1;
}

static void
RefBuildValueContext_AdvanceFormatChar(RefBuildValueContext *aContext)
{
    RefBuildValueContext_AdvanceFormatCharJustOne(aContext);
    RefBuildValueContext_AdvanceFormatCharWhileSpace(aContext);
}

static const char *
RefBuildValueContext_GetCurrentFormatCharPtr(RefBuildValueContext *aContext)
{
    return &aContext->mFormatStr[aContext->mFormatStrIndex];
}


/* 
 * "[]" -> []
 * "[???]" -> [?, ?, ?]
 * "[?, ?, ?]" -> [?, ?, ?]
 */
static RefObject *
RefBuildValueContext_BuildList(RefBuildValueContext *aContext)
{
    RefObject *list;
    int err;

    assert(RefBuildValueContext_GetCurrentFormatChar(aContext) == '[');
    RefBuildValueContext_AdvanceFormatChar(aContext);

    err = 0;
    list = RefList_CreateWithAllocator(aContext->mRefAllocator);
    for (;;) {
        RefObject *value;
        char cur_char;
        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char == ']') {
            /* finish */
            RefBuildValueContext_AdvanceFormatChar(aContext);
            break;
        }
        value = RefBuildValueContext_BuildValue(aContext);
        if (!value) {
            err = 1;
            break;
        }
        RefList_Append(list, value);
        RefObject_DecRef(value);

        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char == ',') {
            RefBuildValueContext_AdvanceFormatChar(aContext);
            cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
            if (cur_char == ']') {
                err = 1;
                break;
            }
        }
    }
    if (err) {
        RefObject_DecRef(list);
        return NULL;
    }
    return list;
}

/* 
 * "{}" -> {}
 * "{s:?, s:?}" -> {s:?, s:?}
 * "{s:?, s:{s:?}}" -> {s:?, {s:?}}
 */
static RefObject *
RefBuildValueContext_BuildDict(RefBuildValueContext *aContext)
{
    RefObject *dict;
    int err;
    const int single_quat = '\'';

    assert(RefBuildValueContext_GetCurrentFormatChar(aContext) == '{');
    RefBuildValueContext_AdvanceFormatChar(aContext);

    err = 0;
    dict = RefDict_CreateWithAllocator(aContext->mRefAllocator);
    for (;;) {
        RefObject *value;
        RefObject *key;
        char cur_char;
        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char == '}') {
            /* finish */
            RefBuildValueContext_AdvanceFormatChar(aContext);
            break;
        }
        /* key */
        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char == single_quat) {
            int i;
            const char *p;
            RefBuildValueContext_AdvanceFormatCharJustOne(aContext); /* skip ' */
            p = RefBuildValueContext_GetCurrentFormatCharPtr(aContext);
            i = 0;
            cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
            while (cur_char != single_quat) {
                i += 1;
                RefBuildValueContext_AdvanceFormatCharJustOne(aContext);
                cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
            }
            /* TODO: check valid string */
            key = RefString_CreateDuplicateWithAllocator((unsigned char *)p, /* TODO: remove cast */
                                                         i, aContext->mRefAllocator);
            RefBuildValueContext_AdvanceFormatChar(aContext); /* skip ' */
        } else {
            key = RefBuildValueContext_BuildValue(aContext);
            if (!key) {
                err = 1;
                break;
            }
            if (!RefString_IsValid(key)) {
                RefObject_DecRef(key);
                err = 1;
                break;
            }
        }
        /* colon */
        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char != ':') {
            RefObject_DecRef(key);
            err = 1;
            break;
        }
        RefBuildValueContext_AdvanceFormatChar(aContext); /* skip ':' */

        /* value */
        value = RefBuildValueContext_BuildValue(aContext);
        if (!value) {
            RefObject_DecRef(key);
            err = 1;
            break;
        }

        /* SetItem */
        RefDict_SetItem(dict, key, value);
        RefObject_DecRef(key);
        RefObject_DecRef(value);

        cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
        if (cur_char == ',') {
            RefBuildValueContext_AdvanceFormatChar(aContext);
            cur_char = RefBuildValueContext_GetCurrentFormatChar(aContext);
            if (cur_char == '}') {
                err = 1;
                break;
            }
        }
    }
    if (err) {
        RefObject_DecRef(dict);
        return NULL;
    }
    return dict;
}

static RefObject *
RefBuildValueContext_BuildValue(RefBuildValueContext *aContext)
{
    RefObject *value;
    switch (RefBuildValueContext_GetCurrentFormatChar(aContext)) {
    case '{':
        value = RefBuildValueContext_BuildDict(aContext);
        break;
    case '[':
        value = RefBuildValueContext_BuildList(aContext);
        break;
    case 'O':
        value = RefBuildValueArgs_GetRefObject(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 's':
        value = RefBuildValueArgs_GetString(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'v':
        value = RefBuildValueArgs_GetVoidPtr(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'i':
        value = RefBuildValueArgs_GetInt(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'u':
        value = RefBuildValueArgs_GetUInt(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'l':
        value = RefBuildValueArgs_GetLong(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'U':
        value = RefBuildValueArgs_GetULong(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'f':
        value = RefBuildValueArgs_GetFloat(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    case 'd':
        value = RefBuildValueArgs_GetDouble(aContext->mArgs, aContext->mRefAllocator);
        RefBuildValueContext_AdvanceFormatChar(aContext);
        break;
    default:
        value = NULL;
        break;
    }
    return value;
}

static RefObject *
RefBuildValue(RefBuildValueContext *aContext)
{
    RefObject *value;
    RefBuildValueContext_AdvanceFormatCharWhileSpace(aContext);
    value = RefBuildValueContext_BuildValue(aContext);
    if (value) {
        RefBuildValueContext_AdvanceFormatCharWhileSpace(aContext);
        if (RefBuildValueContext_GetCurrentFormatChar(aContext) != '\0') {
            /* error */
            RefObject_DecRef(value);
            value = NULL;
        }
    }
    return value;
}


#ifdef REFOBJECT_HAS_STDC_STDARG

static RefObject *
RefBuildValueByVaArgsSub(RefObject *aRefAllocator, const char *aFormatStr, va_list *aVaList)
{
    RefObject *value;
    RefBuildValueContext context;
    RefBuildValueArgs args;

    RefBuildValueArgs_InitializeVaArgs(&args, aVaList);
    RefBuildValueContext_Initialize(&context, aRefAllocator, aFormatStr, &args);

    value = RefBuildValue(&context);

    RefBuildValueContext_Finalize(&context);
    RefBuildValueArgs_Finalize(&args);
    return value;
}

RefObject *
RefBuildValueByVaArgsWithAllocator(RefObject *aRefAllocator, const char *aFormatStr, ...)
{
    va_list args;
    RefObject *built_value;
    va_start(args, aFormatStr);
    built_value = RefBuildValueByVaArgsSub(aRefAllocator, aFormatStr, &args);
    va_end(args);
    return built_value;
}

RefObject *
RefBuildValueByVaArgs(const char *aFormatStr, ...)
{
    va_list args;
    RefObject *built_value;
    va_start(args, aFormatStr);
    built_value = RefBuildValueByVaArgsSub(RefModule_GetSingletonAllocator(), aFormatStr, &args);
    va_end(args);
    return built_value;
}

#endif


RefObject *
RefBuildValueByListWithAllocator(RefObject *aRefAllocator, const char *aFormatStr, RefObject *aRefList)
{
    RefObject *value;
    RefBuildValueContext context;
    RefBuildValueArgs args;

    RefBuildValueArgs_InitializeRefList(&args, aRefList);
    RefBuildValueContext_Initialize(&context, aRefAllocator, aFormatStr, &args);

    value = RefBuildValue(&context);

    RefBuildValueContext_Finalize(&context);
    RefBuildValueArgs_Finalize(&args);

    return value;
}

RefObject *
RefBuildValueByList(const char *aFormatStr, RefObject *aRefList)
{
    return RefBuildValueByListWithAllocator(RefModule_GetSingletonAllocator(), aFormatStr, aRefList);
}

